#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/in.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/ethtool.h>
#include <linux/highmem.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/version.h>
#include <linux/spinlock.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/semaphore.h>
#include <linux/phy.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/irqreturn.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>

#include <asm/irq.h>
#include <asm/page.h>
#include <asm/setup.h>
#include <linux/crc32.h>
#include <mach/pmu.h>
#include "fh_gmac.h"
#include <linux/gpio.h>
#include <mach/fh_gmac_plat.h>
#include "fh_gmac_dts_parse.h"

/*flag define*/
/*| rev | sleep |  pmu or phy_apb own reg field  |  write or write_m |*/

#define W_FLAG	0x01
#define M_FLAG	0x02
#define ACTION_MASK	0xff

#define W_PMU_FLAG	0x100
#define W_PHY_OWN_FLAG	0x200
#define W_PHY_MDIO_FLAG	0x400
#define FIELD_MASK	0xff00

#define S_FLAG	0x10000
#define EXTRA_MASK	0xff0000



struct fh_gmac_dts_parse {
	void __iomem	*ephy_io_reg;
	u32 bit_reset_index;
	u32 reset_gpio;
	struct phy_reg_cfg_list *phy_internal_sel_array;
	struct phy_reg_cfg_list *phy_external_sel_array;
	struct phy_reg_cfg_list *phy_linkup_array;
	struct phy_reg_cfg_list *phy_linkdown_array;
	struct phy_reg_cfg_list *phy_mii_array;
	struct phy_reg_cfg_list *phy_rmii_array;
	struct phy_reg_cfg *phy_mdio_reg_array;
	u32 phy_mdio_reg_array_index;

	struct phy_interface_info *p_phy_info;
	struct phy_reg_cfg_list *p_phy_reset_array[2];
	struct phy_reg_cfg_list *p_phy_100m_array[2];
	struct phy_reg_cfg_list *p_phy_10m_array[2];
	struct s_train_val *p_train[2];
	u32 train_size[2];

	u32 phy_info_index;


};

struct fh_gmac_dts_parse *g_p_dts_parse = 0;

void fh_pmu_ephy_set_reg(unsigned int offset, unsigned int data)
{
	writel(data, g_p_dts_parse->ephy_io_reg + offset);
}


void fh_pmu_ephy_set_m_reg(unsigned int offset,
unsigned int data, unsigned int mask)
{
	unsigned int ret;

	ret = readl(g_p_dts_parse->ephy_io_reg + offset);
	ret &= ~(mask);
	ret |= (data & mask);
	writel(ret, g_p_dts_parse->ephy_io_reg + offset);
}


void dts_gmac_reset(void){
	_pmu_ahb_reset(~(1 << g_p_dts_parse->bit_reset_index), 1000, 1);
}


void dump_phy_train_val(struct s_train_val *p_train)
{
	pr_err("\n------------GAP_LINE---------------\n");
	pr_err("name : %s\n",p_train->name);
	pr_err("src_add : %x\n",p_train->src_add);
	pr_err("src_mask : %x\n",p_train->src_mask);
	pr_err("src_vaild_index : %x\n",p_train->src_vaild_index);
	pr_err("dst_add : %x\n",p_train->dst_add);
	pr_err("dst_vaild_index : %x\n",p_train->dst_vaild_index);
	pr_err("usr_train_offset : %x\n",p_train->usr_train_offset);
}

int parse_reg_array(struct phy_reg_cfg_list *p_array){
	if(!p_array){
		pr_err("parse reg array is null...\n");
		return 0;
	}

	for (; p_array->r_w != 0; p_array++) {
		if ((p_array->r_w & ACTION_MASK) == W_FLAG) {
			if ((p_array->r_w & FIELD_MASK) == W_PMU_FLAG) {
				/*pr_err("fh_pmu_set_reg :: %08x : %08x\n",p_array->reg_add, p_array->reg_val);*/
				fh_pmu_set_reg(p_array->reg_add, p_array->reg_val);
			}
			else if ((p_array->r_w & FIELD_MASK) == W_PHY_OWN_FLAG) {
				/*pr_err("fh_pmu_ephy_set_reg :: %08x : %08x\n",p_array->reg_add, p_array->reg_val);*/
				fh_pmu_ephy_set_reg(p_array->reg_add, p_array->reg_val);
			}
		}

		if ((p_array->r_w & ACTION_MASK) == M_FLAG) {
			if ((p_array->r_w & FIELD_MASK) == W_PMU_FLAG) {
				/*pr_err("fh_pmu_set_reg_m :: %08x : %08x : %08x\n",p_array->reg_add, p_array->reg_val, p_array->reg_mask);*/
				fh_pmu_set_reg_m(p_array->reg_add, p_array->reg_val, p_array->reg_mask);
			}
			else if ((p_array->r_w & FIELD_MASK) == W_PHY_OWN_FLAG) {
				/*pr_err("fh_pmu_set_reg_m :: %08x : %08x : %08x\n",p_array->reg_add, p_array->reg_val, p_array->reg_mask);*/
				fh_pmu_ephy_set_m_reg(p_array->reg_add, p_array->reg_val, p_array->reg_mask);
			}

		}

		if ((p_array->r_w & EXTRA_MASK) == S_FLAG) {
			if (p_array->reg_val < 1000)
				udelay(p_array->reg_val);
			else
				mdelay(p_array->reg_val / 1000);
		}
	}

	return 0;
}


struct phy_reg_cfg_list *get_exphy_array(void){
	return g_p_dts_parse->phy_external_sel_array;
}

struct phy_reg_cfg_list *get_inphy_array(void){
	return g_p_dts_parse->phy_internal_sel_array;
}

int dts_phy_sel(unsigned int sel){
	struct phy_reg_cfg_list *p_array = 0;

	if(sel == EXTERNAL_PHY)
		p_array = get_exphy_array();
	else
		p_array = get_inphy_array();
	return parse_reg_array(p_array);
}


struct phy_reg_cfg_list *get_mii_array(void){
	return g_p_dts_parse->phy_mii_array;
}

struct phy_reg_cfg_list *get_rmii_array(void){
	return g_p_dts_parse->phy_rmii_array;
}

int dts_inf_set(unsigned int inf){
	struct phy_reg_cfg_list *p_array = 0;

	if(inf == PHY_INTERFACE_MODE_MII)
		p_array = get_mii_array();
	else
		p_array = get_rmii_array();
	return parse_reg_array(p_array);
}


void parse_train_data(struct s_train_val *p_train){
	unsigned long test_data;
	int i;
	u32 temp_data;
	u32 vaild_bit_pos;
	int max_offset, low_offset;
	u32 *array;
	u32 size;

	array = p_train->bind_train_array;
	size = p_train->bind_train_size;
	test_data = p_train->src_mask;
	vaild_bit_pos = find_first_bit(&test_data, 32);

	if ((!array) ||
	((p_train->src_mask >> vaild_bit_pos) != (size - 1))) {
		pr_err("para error..\n");
		return;
	}

	temp_data = fh_pmu_get_reg(p_train->src_add);
	if (temp_data & (1 << p_train->src_vaild_index)) {
		temp_data &= p_train->src_mask;
		temp_data >>= vaild_bit_pos;

		for(i = 0; i < size; i++){
			if(temp_data == array[i]){
				max_offset = (size - 1) - i;
				low_offset = 0 - i;
				break;
			}
		}
		/*pr_err("[%s] :: raw val %d; usr offset %d\n",
		p_train->name, temp_data, p_train->usr_train_offset);
		*/
		if ((p_train->usr_train_offset < low_offset) ||
		(p_train->usr_train_offset > max_offset)) {
			pr_err("offset [%d] should limit in [%d , %d]\n",
			p_train->usr_train_offset, low_offset, max_offset);
			return;
		}
		i += p_train->usr_train_offset;
		temp_data = array[i];
	} else {
		temp_data = 0;
		pr_err("[%s] :: use default train data %d\n", p_train->name, temp_data);
	}
	temp_data &= (p_train->src_mask >> vaild_bit_pos);

	if (!strcmp(p_train->dst_base_name, "ephy")) {
		fh_pmu_ephy_set_m_reg(p_train->dst_add, temp_data << p_train->dst_vaild_index,
		(p_train->src_mask >> vaild_bit_pos) << p_train->dst_vaild_index);
	} else {
		pr_err("set pmu train\n");
		fh_pmu_set_reg_m(p_train->dst_add, temp_data << p_train->dst_vaild_index,
		(p_train->src_mask >> vaild_bit_pos) << p_train->dst_vaild_index);
	}

}

void dts_internal_phy_reset(void){
	struct phy_reg_cfg_list *p_array = 0;
	int i;
	int j;
	struct s_train_val *p_train;
	u32 train_size;

	for(i = 0; i < 2; i++){
		if(g_p_dts_parse->p_phy_info[i].phy_sel == INTERNAL_PHY){
			p_array = g_p_dts_parse->p_phy_reset_array[i];
			break;
		}
	}
	parse_reg_array(p_array);

	if(i < 2){
		//if has train data..
		p_train = g_p_dts_parse->p_train[i];
		train_size = g_p_dts_parse->train_size[i];
		if(p_train){
			for(j = 0; j < train_size; j++){
				parse_train_data(p_train);
				p_train++;
			}
		}
	}
}

void dts_external_phy_reset(void){
	int ret;
	int reset_pin = g_p_dts_parse->reset_gpio;
	ret = gpio_request(reset_pin, "phyreset-gpio");
	if (ret) {
		pr_err("%s: ERROR: request reset pin : %d failed\n",
		__func__, reset_pin);
		return;
	}

	gpio_direction_output(reset_pin, GPIOF_OUT_INIT_HIGH);
	usleep_range(150000, 200000);
	gpio_direction_output(reset_pin, GPIOF_OUT_INIT_LOW);
	/* actual is 20ms */
	usleep_range(10000, 20000);
	gpio_direction_output(reset_pin, GPIOF_OUT_INIT_HIGH);
	usleep_range(10000, 20000);
	gpio_free(reset_pin);
}


int dts_internal_sync_mac_spd(unsigned int spd){
	struct phy_reg_cfg_list *p_array = 0;
	int i;

	if(spd == 100)
		for(i = 0; i < 2; i++){
			if(g_p_dts_parse->p_phy_info[i].phy_sel == INTERNAL_PHY){
				p_array = g_p_dts_parse->p_phy_100m_array[i];
				break;
			}
		}
	else{
		for(i = 0; i < 2; i++){
			if(g_p_dts_parse->p_phy_info[i].phy_sel == INTERNAL_PHY){
				p_array = g_p_dts_parse->p_phy_10m_array[i];
				break;
			}
		}
	}
	return parse_reg_array(p_array);
}


int dts_external_sync_mac_spd(unsigned int spd){
	struct phy_reg_cfg_list *p_array = 0;
	int i;
	if(spd == 100)
		for(i = 0; i < 2; i++){
			if(g_p_dts_parse->p_phy_info[i].phy_sel == EXTERNAL_PHY){
				p_array = g_p_dts_parse->p_phy_100m_array[i];
				break;
			}
		}
	else{
		for(i = 0; i < 2; i++){
			if(g_p_dts_parse->p_phy_info[i].phy_sel == EXTERNAL_PHY){
				p_array = g_p_dts_parse->p_phy_10m_array[i];
				break;
			}
		}
	}
	return parse_reg_array(p_array);
}


struct phy_reg_cfg_list * parse_mem_reg_list(struct property *prop){
	struct phy_reg_cfg_list *p_cfg;
	struct phy_reg_cfg_list *p_raw_cfg;
	__be32 *val;
	int len, i;

	len = prop->length;
	p_cfg = kzalloc(len, GFP_KERNEL);
	if(!p_cfg){
		pr_err("no mem to malloc...\n");
		BUG_ON(1);
	}

	p_raw_cfg = p_cfg;
	val = prop->value;
	for (i = 0; i < len / 16; i++) {
		p_cfg->r_w = be32_to_cpup(val++);
		p_cfg->reg_add = be32_to_cpup(val++);
		p_cfg->reg_val = be32_to_cpup(val++);
		p_cfg->reg_mask = be32_to_cpup(val++);
		p_cfg++;
	}
	return p_raw_cfg;
}



u32 *parse_train_list(struct property *prop, u32 *array_size){
	__be32 *val;
	int len, i;
	u32 *p_ret;

	len = prop->length;
	val = prop->value;

	p_ret = kzalloc(len, GFP_KERNEL);
	if (!p_ret) {
		pr_err("no mem to malloc...\n");
		BUG_ON(1);
	}
	*array_size = len / 4;

	for (i = 0; i < len / 4; i++)
		p_ret[i] = be32_to_cpup(val++);

	return p_ret;
}



void parse_mdio_reg_list(struct device_node *np, struct fh_gmac_dts_parse *p_dts){
	u32 temp;
	struct property *prop;
	__be32 *val;
	u32 i;

	struct phy_reg_cfg *p_reg_array = &p_dts->phy_mdio_reg_array[p_dts->phy_mdio_reg_array_index];

	if (!of_property_read_u32(np, "id", &temp))
		p_reg_array->id = temp;
	prop = of_find_property(np, "mdio,reg_list", NULL);

	val = prop->value;
	if (prop && prop->value) {
		for (i = 0; i < prop->length / 16; i++) {
			p_reg_array->list[i].r_w = be32_to_cpup(val++);
			p_reg_array->list[i].reg_add = be32_to_cpup(val++);
			p_reg_array->list[i].reg_val = be32_to_cpup(val++);
			p_reg_array->list[i].reg_mask = be32_to_cpup(val++);
		}
	}
	p_dts->phy_mdio_reg_array_index++;
}


void parse_phy_sel_para(struct device_node *np, struct fh_gmac_dts_parse *p_dts){
	struct property *prop;

	prop = of_find_property(np, "internal_reset,reg_list", NULL);
	if (prop && prop->value){
		p_dts->phy_internal_sel_array = parse_mem_reg_list(prop);
	}

	prop = of_find_property(np, "external_reset,reg_list", NULL);
	if (prop && prop->value){
		p_dts->phy_external_sel_array = parse_mem_reg_list(prop);
	}

}

void parse_phy_link_para(struct device_node *np, struct fh_gmac_dts_parse *p_dts) {
	struct property *prop;

	prop = of_find_property(np, "linkup,reg_list", NULL);
	if (prop && prop->value)
		p_dts->phy_linkup_array = parse_mem_reg_list(prop);

	prop = of_find_property(np, "linkdown,reg_list", NULL);
	if (prop && prop->value)
		p_dts->phy_linkdown_array = parse_mem_reg_list(prop);

}


void parse_phy_if_para(struct device_node *np, struct fh_gmac_dts_parse *p_dts) {
	struct property *prop;

	prop = of_find_property(np, "mii,reg_list", NULL);
	if (prop && prop->value)
		p_dts->phy_mii_array = parse_mem_reg_list(prop);

	prop = of_find_property(np, "rmii,reg_list", NULL);
	if (prop && prop->value)
		p_dts->phy_rmii_array = parse_mem_reg_list(prop);
}

void parse_phy_info(struct device_node *np, struct fh_gmac_dts_parse *p_dts){
	struct property *prop;
	struct device_node *child;
	u32 temp;
	s32 s_temp;
	struct s_train_val *p_train;

	struct phy_interface_info *p_info;

	if(p_dts->phy_info_index > 1)
		BUG_ON(1);
	p_info = &p_dts->p_phy_info[p_dts->phy_info_index];

	if (strcmp("internal", of_get_property(np, "phy_sel", &temp)) == 0) {
		p_info->phy_sel = INTERNAL_PHY;
		p_info->phy_reset = dts_internal_phy_reset;
		p_info->sync_mac_spd = dts_internal_sync_mac_spd;
	} else {
		p_info->phy_sel = EXTERNAL_PHY;
		p_info->phy_reset = dts_external_phy_reset;
		p_info->sync_mac_spd = dts_external_sync_mac_spd;
	}

	if (strcmp("mii", of_get_property(np, "phy_inf", &temp)) == 0)
		p_info->phy_inf = PHY_INTERFACE_MODE_MII;
	else
		p_info->phy_inf = PHY_INTERFACE_MODE_RMII;

	prop = of_find_property(np, "phy_reset,reg_list", NULL);
	if (prop && prop->value)
		p_dts->p_phy_reset_array[p_dts->phy_info_index] = parse_mem_reg_list(prop);

	prop = of_find_property(np, "sync_mac_spd_100,reg_list", NULL);
	if (prop && prop->value)
		p_dts->p_phy_100m_array[p_dts->phy_info_index] = parse_mem_reg_list(prop);

	prop = of_find_property(np, "sync_mac_spd_10,reg_list", NULL);
	if (prop && prop->value)
		p_dts->p_phy_10m_array[p_dts->phy_info_index] = parse_mem_reg_list(prop);


	for_each_child_of_node(np, child){
		p_dts->train_size[p_dts->phy_info_index]++;
	}

	if (p_dts->train_size[p_dts->phy_info_index]) {
		p_dts->p_train[p_dts->phy_info_index] = kzalloc(sizeof(struct s_train_val) *
		p_dts->train_size[p_dts->phy_info_index], GFP_KERNEL);
		if (!p_dts->p_train[p_dts->phy_info_index]) {
			pr_err("no mem to malloc..\n");
			BUG_ON(1);
		}
		p_train = p_dts->p_train[p_dts->phy_info_index];
		for_each_child_of_node(np, child){
			p_train->name = of_get_property(child, "train_name", 0);
			p_train->dst_base_name = of_get_property(child, "dst_base_name", 0);
			if (!of_property_read_u32(child, "src_add", &temp))
				p_train->src_add = temp;

			if (!of_property_read_u32(child, "src_mask", &temp))
				p_train->src_mask = temp;

			if (!of_property_read_u32(child, "src_vaild_index", &temp))
				p_train->src_vaild_index = temp;

			if (!of_property_read_u32(child, "dst_add", &temp))
				p_train->dst_add = temp;

			if (!of_property_read_u32(child, "dst_vaild_index", &temp))
				p_train->dst_vaild_index = temp;

			if (!of_property_read_s32(child, "usr_train_offset", &s_temp))
				p_train->usr_train_offset = s_temp;

			prop = of_find_property(child, "train_array,reg_list", NULL);
			if (prop && prop->value)
				p_train->bind_train_array =
				parse_train_list(prop, &p_train->bind_train_size);
			/*dump_phy_train_val(p_train);*/
			p_train++;
		}
	}
	p_dts->phy_info_index++;
}

static const struct of_device_id fh_ephy_of_match[] = {

	{.compatible = "fh,fh-ephy",},
	{},
};
MODULE_DEVICE_TABLE(of, fh_ephy_of_match);

struct phy_reg_cfg_list *get_phy_linkup_array(void) {
	return g_p_dts_parse->phy_linkup_array;
}

struct phy_reg_cfg_list *get_phy_linkdown_array(void) {
	return g_p_dts_parse->phy_linkdown_array;
}

void gmac_linkup_board_process(void)
{
	struct phy_reg_cfg_list *p_array = 0;

	p_array = get_phy_linkup_array();
	parse_reg_array(p_array);
}

void gmac_linkdown_board_process(void)
{
	struct phy_reg_cfg_list *p_array = 0;

	p_array = get_phy_linkdown_array();
	parse_reg_array(p_array);
}

struct fh_gmac_platform_data *fh_gmac_parse_dts_node(struct platform_device *pdev){
	struct device_node *np = pdev->dev.of_node;
	struct device_node *io_np;
	struct device_node *child;
	struct fh_gmac_platform_data *ret_plat;
	int temp;

	u32 mdio_setting_size = 0;
	struct fh_gmac_dts_parse *p_dts_parse = 0;

	p_dts_parse = kzalloc(sizeof(struct fh_gmac_dts_parse), GFP_KERNEL);
	if (!p_dts_parse) {
		pr_err("no mem to malloc...\n");
		return 0;
	}

	io_np = of_find_matching_node(NULL, fh_ephy_of_match);
	if (io_np)
		p_dts_parse->ephy_io_reg = of_iomap(io_np, 0);

	ret_plat = kzalloc(sizeof(struct fh_gmac_platform_data), GFP_KERNEL);
	if (!ret_plat) {
		kfree(p_dts_parse);
		pr_err("no mem to malloc...\n");
		return 0;
	}

	for_each_child_of_node(np, child) {
		if (!strcmp(child->name, "phy_mdio_setting"))
			mdio_setting_size++;
	}

	p_dts_parse->phy_mdio_reg_array = kzalloc(sizeof(struct phy_reg_cfg) *
	mdio_setting_size, GFP_KERNEL);
	if (!p_dts_parse->phy_mdio_reg_array) {
		kfree(ret_plat);
		kfree(p_dts_parse);
		pr_err("no mem to malloc...\n");
		return 0;
	}
	g_p_dts_parse = p_dts_parse;

	p_dts_parse->p_phy_info = ret_plat->phy_info;

	if (!of_property_read_u32(np, "ahb_reset_bit_index", &temp))
		p_dts_parse->bit_reset_index = temp;

	if (!of_property_read_u32(np, "phyreset-gpio", &temp))
		p_dts_parse->reset_gpio = temp;

	for_each_child_of_node(np, child) {
		/*pr_err("child name is [%s]\n",child->name);*/
		if (!strcmp(child->name, "phy_mdio_setting"))
			parse_mdio_reg_list(child, p_dts_parse);

		if (!strcmp(child->name, "phy_sel_set"))
			parse_phy_sel_para(child, p_dts_parse);

		if (!strcmp(child->name, "phy_interface_set"))
			parse_phy_if_para(child, p_dts_parse);

		if (!strcmp(child->name, "phy_link_process"))
			parse_phy_link_para(child, p_dts_parse);

		if (!strcmp(child->name, "phy_info"))
			parse_phy_info(child, p_dts_parse);
	}

	/*bind parse to ret platform*/
	ret_plat->p_cfg_array = p_dts_parse->phy_mdio_reg_array;
	ret_plat->gmac_reset = dts_gmac_reset;
	ret_plat->phy_sel = dts_phy_sel;
	ret_plat->inf_set = dts_inf_set;
	pr_err("\nfh_gmac_parse_dts_node done...\n");
	return ret_plat;
}
